#!/usr/bin/python3
# -*- coding: utf-8 -*-
'''
@File    :  edp_sdk.py
@Time    :  2022/05/02 17:12:14
@Author  :  HHLiang
@Contact :  sternapeg@qq.com
@Version :  1.0
@Desc    :  EDP协议的SKD，包含了设备连接、数据上传、命令下发等功能的实现代码。
            gen_conn_msg: 生成连接信息
            save_typeX,X表示对于的1~7: 表示7种上传数据的类型
            gen_push_msg: 点对点信息透传
            recv_data_parser: 接收平台下发命令
'''

import sys
import struct
import json
import socket
import time

import requests

# 计算消息长度
def _message_len(i):                        
    """
    它将一个整数转换为一系列字节，其中每个字节是整数模 128，最后一个字节是整数除以 128

    :param i: 消息的长度
    :return: 消息的长度。
    """

    res = []
    while i > 0:
        temp = i % 128
        i = i // 128
        if i > 0:
            res.append(temp + 128)
        elif i == 0:
            res.append(temp)
    return bytes(res)


def check_json_format(json_msg):
    """
    用于判断一个字符串是否符合Json格式
    """
    if isinstance(json_msg, str):  # 首先判断变量是否为字符串
        try:
            json.loads(json_msg)
        except ValueError:
            print(ValueError)
            return False
        return True
    else:
        return False


def gen_conn_msg(pid=None, auth_info=None):
    """
    生成连接信息
    基于登录平台的连接请求消息协议格式（产品ID+AuthInfo），开发的连接请求

    :param pid: 设备编号
    :param auth_info: 设备的信息
    :return: 一个字节串。
    """
    msg_type    = b'\x10'          # b1    鉴权连接固定包头 0x10
    proto_desc  = b'\x00\x03EDP'   # b3_7  协议描述
    proto_ver   = b'\x01'          # b8    协议版本号1
    
    if pid and auth_info:
        conn_flag = b'\xc0'                                # b9     连接标志 二进制数1100 0000，对应十六进制c0
        
        # 要求首位为！即大端模式标准对齐方式，故输出的高位就会自动放在内存的高地址位，H inter类型
        keepalive = struct.pack('!H', 300)                 # b10_11 保活时间
        device = b'\x00\x00'                               # b12_13 设备ID

        pid_len = struct.pack('!H', len(pid))              # b14_15 产品ID长度
        pid = pid.encode('utf-8')                          # b16_n  产品ID 可变长度，n=16+len(pid)
        # 将auth_info字符串的长度转换为二进制2字节数据。
        auth_info_len = struct.pack('!H', len(auth_info))  # b(n+1)_(n+2)   auth_info长度
        # print('auth_info', auth_info_len)
        auth_info = auth_info.encode('utf-8')              # b(n+3)_(n+3+l) auth_info信息内容 l=len(auth_info)

        auth = pid_len + pid + auth_info_len + auth_info   # b14_最后位数
    else:
        print('CONN_REQ: params error, request params are not given!')
        raise Exception

    rest = proto_desc + proto_ver + conn_flag + keepalive + device + auth # 剩余消息内容
    rest_len = bytes([len(rest)])                          # b2 消息剩余字节长度

    conn_msg = msg_type + rest_len + rest                  # 连接请求消息

    return conn_msg


def save_type1(data, msg_id):
    """
    它接受一个 JSON 对象，将其转换为字节字符串，然后在其前面添加一个标头

    :param data: 要发送的数据，json格式
    :param msg_id: 消息id，用于标识消息。
    :param dev_id: 设备编号
    :return: 一个字节串。
    """
    # 数据类型格式1--JSON格式数据
    data_type = b'\x01'                         # json格式1 数据类型        b15
    if check_json_format(data):
        
        # data = json.dumps(data)   # 书上内容错误
        # print('---1---data: ',len(data))
        data_len = struct.pack('!H', len(data)) # json数据流长度,16进制表示  b16~17
        # print('---2---data len: ',data_len)
        data = data.encode('utf-8')             # 消息内容转为二进制数据      b18~b(18+len(data))
        # print('---3---data len: ',len(data))
        body = data_type + data_len + data      # 消息体   b15~b(18+len(data))
        # print('---3---body: ',body)

        msg_type = b'\x80'                      # 固定消息头 0X80         b1
        msg_flag = bytes([int('01000000', 2)])  # 标志,后面有2字节消息编号  b5
        # print('---4---',msg_flag)
        device = b''                            # 目的地址,b5[0]置1则该为置null b6~12
        msg_id = struct.pack('!H', msg_id)      # 消息编号,b5[1]位置1则需要消息编号 b13~14
        rest_head = msg_flag + device + msg_id  # 消息头                 b5~14
        # print('---4---',rest_head)
        rest = rest_head + body                 # 请求消息：消息头+消息体   b5~n
        # print('---4---rest: ',rest)
        rest_len = _message_len(len(rest))      # 剩余消息长度               b2~4
        # print('---4---rest_len',rest_len)
        msg = msg_type + rest_len + rest        # 消息组包完成
        # print('---5---msg: ',msg)

        return msg

    else:
        print('数据错误，请使用JSON格式的数据')
        pass


def save_type3(data, msg_id):
    # 数据类型格式3--JSON格式数据
    data_type = b'\x03'                         # json格式3 数据类型        b15
    if check_json_format(data):
        
        data_len = struct.pack('!H', len(data)) # json数据流长度,16进制表示  b16~17
        data = data.encode('utf-8')             # 消息内容转为二进制数据      b18~b(18+len(data))
        body = data_type + data_len + data      # 消息体   b15~b(18+len(data))

        msg_type = b'\x80'                      # 固定消息头 0X80         b1
        msg_flag = bytes([int('01000000', 2)])  # 标志,后面有2字节消息编号  b5
        device = b''                            # 目的地址,b5[0]置1则该为置null b6~12
        msg_id = struct.pack('!H', msg_id)      # 消息编号,b5[1]位置1则需要消息编号 b13~14
        rest_head = msg_flag + device + msg_id  # 消息头                 b5~14
        rest = rest_head + body                 # 请求消息：消息头+消息体   b5~n
        rest_len = _message_len(len(rest))      # 消息长度               b2~4
        msg = msg_type + rest_len + rest        # 消息组包完成

        return msg

    else:
        print('数据错误，请使用JSON格式的数据')
        pass


def save_type4(data, msg_id):
    """数据类型格式4--JSON格式数据""" 
    data_type = b'\x04'                         # json格式4 数据类型        b15
    if check_json_format(data):
        
        data_len = struct.pack('!H', len(data)) # json数据流长度,16进制表示  b16~17
        data = data.encode('utf-8')             # 消息内容转为二进制数据      b18~b(18+len(data))
        body = data_type + data_len + data      # 消息体   b15~b(18+len(data))

        msg_type = b'\x80'                      # 固定消息头 0X80         b1
        msg_flag = bytes([int('01000000', 2)])  # 标志,后面有2字节消息编号  b5
        device = b''                            # 目的地址,b5[0]置1则该为置null b6~12
        msg_id = struct.pack('!H', msg_id)      # 消息编号,b5[1]位置1则需要消息编号 b13~14
        rest_head = msg_flag + device + msg_id  # 消息头                 b5~14
        rest = rest_head + body                 # 请求消息：消息头+消息体   b5~n
        rest_len = _message_len(len(rest))      # 消息长度               b2~4
        msg = msg_type + rest_len + rest        # 消息组包完成

        return msg

    else:
        print('数据错误，请使用JSON格式的数据')
        pass

def save_type5(data, msg_id):
    # 数据类型格式5--分号间隔字符串
  
    data_type = b'\x05'                     # json格式5 数据类型        b15
    data_len = struct.pack('!H', len(data)) # json数据流长度,16进制表示  b16~17
    data = data.encode('utf-8')             # 消息内容转为二进制数据      b18~b(18+len(data))
    body = data_type + data_len + data      # 消息体   b15~b(18+len(data))

    msg_type = b'\x80'                      # 固定消息头 0X80         b1
    msg_flag = bytes([int('01000000', 2)])  # 标志,后面有2字节消息编号  b5
    device = b''                            # 目的地址,b5[0]置1则该为置null b6~12
    msg_id = struct.pack('!H', msg_id)      # 消息编号,b5[1]位置1则需要消息编号 b13~14
    rest_head = msg_flag + device + msg_id  # 消息头                 b5~14
    rest = rest_head + body                 # 请求消息：消息头+消息体   b5~n
    rest_len = _message_len(len(rest))      # 消息长度               b2~4
    msg = msg_type + rest_len + rest        # 消息组包完成

    return msg

def recv_data_parser(recv_data):
    """
    如果接收到的数据的第一个字节为0x90，那么该消息为确认消息，返回消息ID和确认结果

    :param recv_data: 服务器接收到的数据
    :return: 返回值是两个值的元组。第一个值是消息 ID，第二个值是结果。
    """
    if not recv_data:   # 未接收到消息
        sys.exit()

    elif recv_data[0] == 0x90:
        # 消息头为90，save_data确认消息
        msg_id = struct.unpack('!H', recv_data[3:5])[0]
        if recv_data[-1] == 0:
            res = True
        else:
            res = False
        return msg_id, res

    elif recv_data[0] == 0x20:
        # 消息头为20，连接确认消息
        pass

    elif recv_data[0] == 0xA0:
        # 消息头为A0，命令消息
        print('recv_data',recv_data)
        body_len, length_len = calc_body_len(recv_data)
        mark = length_len + 1
        print('mark: ',mark)
        cmdid_len = recv_data[mark:mark+2]
        print('cmdid_len: ',cmdid_len)
        mark += 2
        cmdid_len = struct.unpack('!H', cmdid_len)[0]
        print('cmdid_len: ', cmdid_len)
        cmd_id = recv_data[mark:mark+cmdid_len]
        mark += cmdid_len
        cmdbody_len = recv_data[mark:mark+4]
        mark += 4
        cmd_body = recv_data[mark:]
        return cmd_id, cmd_body

    elif recv_data[0] == 0xD0:
        # 消息头为D0，心跳响应
        pass

    elif recv_data[0] == 0x40:
        # 消息头为40，错误
        return False, False


# 计算消息体长度
def calc_body_len(r_msg):
    """
    它接受一个字节列表，并返回消息正文的长度，以及用于编码正文长度的字节数

    :param r_msg: 从服务器收到的消息
    :return: 正文长度和用于对正文长度进行编码的字节数。
    """
    res = []
    for x in range(4):
        if r_msg[x+1] > 128:
            res.append(r_msg[x+1] - 128)
        else:
            res.append(r_msg[x+1])
            break
        if x == 3 and r_msg[x+1] > 128:
            print('Error:wrong body length!')
            return
    body_len = 0
    for x in range(len(res)):
        body_len += res[x]*128**x
    return body_len, len(res)


def gen_push_msg(target_device, msg):
    """
    它接受一个目标设备和一条消息，并返回一个可以发送到服务器的字节串
    
    :param target_device: 目标设备ID，即要推送的设备的设备ID。如果为空，则表示所有设备都被推送。
    :param msg: 要发送的消息。
    :return: 返回值是一个字节串。
    """
    msg_type = b'\x30'  # 消息头，b1
    if target_device:  # 如果设置目标设备
        dev_id_len = struct.pack('!H', len(target_device))  # b4~5
        dev_id = target_device.encode('utf-8')              # b6~6+len(target_device)
        device = dev_id_len + dev_id    # 目标地址 b4~6+len(target_device)
    else:
        device = b''

    if type(msg) == str:
        msg = msg.encode('utf-8')   # 消息体 二进制数据流
    elif type(msg) == bytes:
        pass
    else:
        print('PUSH_DATA:message unacceptable!')
        raise Exception

    rest = device + msg
    rest_len = _message_len(len(rest))

    return msg_type + rest_len + rest

def cmd_reply(cmd_id, cmd_resp):
    msg_type = b'\xB0'
    cmd_len = struct.pack('!H', len(cmd_id))     # H C(unsigned short)  PY(integer)
    cmd = cmd_len + cmd_id
    resp_len = struct.pack('!I', len(cmd_resp))  # I C(unsigned int)   PY(integer)
    resp = resp_len + cmd_resp
    rest = cmd + resp
    rest_len = _message_len(len(rest))

    msg = msg_type + rest_len + rest
    return msg

def check_cmd_resp(cmd_id, api_key):
    url = 'http://api.heclouds.com/cmds/%s/resp' % cmd_id.decode()
    headers = {'api-key': api_key}
    r = requests.get(url, headers=headers)
    print('API查询命令响应结果:', r.text)


if __name__ == '__main__':

    raw_msg = '{"datastreams": [{"id": "temp", "datapoints": [{"value": 32.6}]}]}'
    a = check_json_format(raw_msg)
    print(a)
